import {PianoNote, PianoSheet} from "../../services/Piano";
import * as Tone from "tone";

import {useCallback, useEffect, useState} from "react";

import {Graphics, useTick} from '@pixi/react';

import {Graphics as PixiGraphics} from '@pixi/graphics'

import './PianoWaterfall.css';
import {useCameraView} from "../CameraView/CameraViewContext.ts";
import {PianoKeyboardElement, usePianoKeyboard} from "../PianoKey/PianoKeyContext.ts";


export interface PianoWaterfallProps {
    pianoSheet: PianoSheet,
    startTimeSeconds: number,
    onNoteAttacked: (notes: PianoNote[]) => void,
    onNoteTriggered: (notes: PianoNote[]) => void,
}

export function PianoWaterfall(props: PianoWaterfallProps) {

    const [notes, setNotes] = useState<PianoNote[]>([]);

    const {topAreaHeight, pianoKeyboard} = usePianoKeyboard();

    // 最终要渲染的音符
    const [noteRects, setNoteRects] = useState<NoteRect[]>([]);

    const {cameraViewOffsetX} = useCameraView();

    useEffect(() => {
        setNotes(getAllNotes(props.pianoSheet));
    }, [props.pianoSheet]);

    useTick(() => {
        // 以当前游戏启动时间作为基准 0, 计算经历的秒数
        const relativeNow = Tone.now() - props.startTimeSeconds;

        const {
            triggeredNotes,
            attackedNotes,
            readyNotes,
            remainNotes
        } = pollReadyNotes(notes, relativeNow, relativeNow + topAreaHeight / 100);

        setNotes(remainNotes);

        setNoteRects(renderReadyNotes(readyNotes, cameraViewOffsetX, topAreaHeight, relativeNow, pianoKeyboard));

        props.onNoteAttacked(attackedNotes);

        props.onNoteTriggered(triggeredNotes);
    })

    const draw = useCallback(
        (g: PixiGraphics) => {
            g.clear();

            for (const noteRect of noteRects) {
                g.beginFill(noteRect.color);
                g.drawRoundedRect(noteRect.x, noteRect.y, noteRect.width, noteRect.height, 8);
                g.endFill();
            }
        },
        [noteRects],
    );

    return <Graphics draw={draw}/>;
}

/**
 * 一个音符在瀑布模式下渲染的真实形状
 */
interface NoteRect {
    x: number;
    y: number;
    width: number;
    height: number;
    color: string; // #83A8DF
}

/**
 * 起始时间和结束时间均不是绝对时间 （如使用Tone.now 获取）, 而是MIDI中的相对时间
 * @param notes  PianoNote[], 该音符数组以按演奏时间排序
 * @param startTime
 * @param endTime
 * @returns { attackedNotes: PianoNote[], readyNotes: PianoNote[], remainNotes: PianoNote[] }
 * readyNotes 是在 startTime 和 endTime 之间的音符, 会渲染在瀑布区域，
 * remainNotes 是在 startTime 之后的音符，在当前时间或者未来时间会被渲染。
 * 已经演奏过得音符不会被返回
 */
function pollReadyNotes(notes: PianoNote[], startTime: number, endTime: number): {
    // 刚按下的音符
    triggeredNotes: PianoNote[],
    // 已经按下的音符
    attackedNotes: PianoNote[],
    // 需要显示在屏幕上的音符, 包括已经按下但是还显示在屏幕中的
    readyNotes: PianoNote[],
    // 剩余音符，包括已经按下但是还显示在屏幕中的
    remainNotes: PianoNote[]
} {
    const triggeredNotes: PianoNote[] = [];

    const readyNotes: PianoNote[] = [];

    const attackedNotes: PianoNote[] = [];

    let lastEndedNodeIndex: number = 0;

    for (let i = 0; i < notes.length; i++) {

        const noteEndTime = notes[i].time + notes[i].duration;

        if (noteEndTime < startTime) {
            lastEndedNodeIndex = i;
        } else if (noteEndTime >= startTime && notes[i].time <= endTime) {
            readyNotes.push(notes[i]);

            if (notes[i].time <= startTime) {
                attackedNotes.push(notes[i]);

                if (!notes[i].attacked) {
                    triggeredNotes.push(notes[i]);
                    notes[i].attacked = true;
                }
            }
        } else if (notes[i].time > endTime) {
            break;
        }
    }

    return {
        triggeredNotes,
        attackedNotes,
        readyNotes,
        remainNotes: notes.slice(lastEndedNodeIndex)
    };
}

function renderReadyNotes(notes: PianoNote[], screenOffsetX: number, height: number, now: number, pianoKeyboardElement: PianoKeyboardElement | null): NoteRect[] {
    if (!pianoKeyboardElement) {
        console.warn("Piano keyboard not ready.")
        return [];
    }

    const noteRects: NoteRect[] = []

    for (const note of notes) {

        const key = pianoKeyboardElement.getPianoKey(note.name);

        if (key) {

            let color = "";

            if (key.type === 'white') {

                if (note.trackId === 0) {
                    color = "#83A8DF";
                } else {
                    color = "#FFC90E";
                }
            } else {

                if (note.trackId === 0) {
                    color = "#2D68C6";
                } else {
                    color = "#FF7F27";
                }
            }

            noteRects.push({
                x: (key.offsetX || 0) - screenOffsetX,
                y: height - (note.time - now) * 100 - note.duration * 100,
                width: key.width,
                height: note.duration * 100,
                color: color
            });
        } else {
            console.error("note" + note.name + " not found.")
        }
    }

    return noteRects;
}

function getAllNotes(pianoSheet: PianoSheet | undefined | null): PianoNote[] {
    if (!pianoSheet) {
        return [];
    }

    const notes = [];

    for (const track of pianoSheet.tracks) {
        for (const node of track.notes) {
            notes.push(node);
        }
    }

    return notes.sort((a, b) => a.time - b.time);
}